package EA;
import EA.Framework;
import RKUjava.lang.RKUCloneable;
import RKUjava.util.RKUStringUtils;

/** Realvalue vector genomes for numerical optimizations. */
public class RealNumericalGenome extends NumericalGenome implements RKUCloneable
{
  /** The RealNumericalFramework this genome is a part of. */
  public RealNumericalFramework rnf;

  public double[] sigmas; 

  /** Create a new realnumerical genome. 
      @param frame The framework for the EA this genome is a part of. 
      This must be a subclass of RealNumericalFramework.
  */
  public RealNumericalGenome(Framework frame, boolean initialize)
    {
      super(frame);
      rnf = (RealNumericalFramework)frame;
      sigmas = new double[rnf.dimensions];

      for(int i=0;i<rnf.dimensions;i++) {
	sigmas[i] = 1;
      }

      realpos = new double[rnf.dimensions];
      if (initialize) {
	for(int i=0;i<rnf.dimensions;i++) {
	  realpos[i] = UsefulRoutines.getUniformDistributed(rnf.intervals[i].min,rnf.intervals[i].max);
	}
      }

    }

  /** Create a new realnumerical genome. 
      @param frame The framework for the EA this genome is a part of. 
      This must be a subclass of RealNumericalFramework.
      @param pos Position in the search space.
  */
  public RealNumericalGenome(Framework frame, double[] pos)
    {
      super(frame);
      rnf = (RealNumericalFramework)frame;
      setPos(pos);
      sigmas = new double[rnf.dimensions];

      for(int i=0;i<rnf.dimensions;i++) {
	sigmas[i] = 1;
      }
    }

  /** Create a new realnumerical genome. 
      @param frame The framework for the EA this genome is a part of. 
      This must be a subclass of RealNumericalFramework.
      @param pos Position in the search space.
      @param sig Sigma vector for diviation.
  */
  public RealNumericalGenome(Framework frame, double[] pos, double[] sig)
    {
      super(frame);
      rnf = (RealNumericalFramework)frame;
      setPos(pos);
      sigmas = sig;
    }

  /** Don't override this method unless you know what you are doing.
      It is called from mutate(p_m) in class Genome and mutates the genome according to 
      the sigmas vector. p_m is not used in this subclass of Genome.
  */

  public void Genome_mutate_inner(double p_m)
    {
      double adder = 0;
      realposcalculated = false;
      
      if (UsefulRoutines.randomBiasedBoolean(p_m)) {
	  //	  System.out.print("Mutated! old="+this.toString());
	for (int i=0;i<rnf.dimensions;i++) {
	  adder = UsefulRoutines.getNormalDistributed(0,sigmas[i]*rnf.intervals[i].getLength()*rnf.intervalpartition);
	  //	  System.out.println("mutdiv="+sigmas[i]*rnf.intervals[i].getLength()*rnf.intervalpartition+" adder="+adder);
	  if (adder+realpos[i]>rnf.intervals[i].max)
	    realpos[i] = rnf.intervals[i].max;
	  else if (adder+realpos[i]<rnf.intervals[i].min) 
	    realpos[i] = rnf.intervals[i].min;
	  else
	    realpos[i] = realpos[i]+adder;
	}
	//	  System.out.println("  new="+this.toString()+"  IP="+rnf.intervalpartition+"  sigmas="+RKUStringUtils.arrayToString(sigmas));
      }
    }

  /** Apply normal distributed mutation with variance <tt>var</tt>. */
  public void mutate(double p_m, double var)
    {
      double adder = 0;
      fitnesscalculated = false;
      realposcalculated = false;

      if (UsefulRoutines.randomBiasedBoolean(p_m)) {
	for (int i=0;i<rnf.dimensions;i++) {
	  adder = UsefulRoutines.getNormalDistributed(0,var*rnf.intervals[i].getLength()*rnf.intervalpartition);
	  //	  System.out.println("mutdiv="+var*rnf.intervals[i].getLength()*rnf.intervalpartition+" adder="+adder);
	  if (adder+realpos[i]>rnf.intervals[i].max)
	    realpos[i] = rnf.intervals[i].max;
	  else if (adder+realpos[i]<rnf.intervals[i].min) 
	    realpos[i] = rnf.intervals[i].min;
	  else
	    realpos[i] = realpos[i]+adder;
	}
      }
    }

  /** Don't override this unless you know what you are doing. <br> 
      The class implements single point crossover on the genome.
      This is done by simply generating a random number r between 0 and the number of dimensions 
      specified in the framework. Then it calls the method crossover(genome1, genome2, [r]) (The other
      crossover method in this class.)
   */
  public static Genome crossover(Genome genome1, Genome genome2)
    {
      int[] arr = {UsefulRoutines.randomInt(((RealNumericalGenome)genome1).rnf.dimensions)};

      return crossover(genome1, genome2, arr);
    }

  /** Performs a variant of uniform crossover between this genome and otherGenome. 
      For the variables <b>not</b> specified in cvariables the child gets the variable from
      a randomly chosen of the two parents. For the variables in cvariables the child's 
      variable is A*p1 + (1-A)*p2, where A is in [0,1]. If your call this method with cvariables as
      the empty array, then it will generate a child with position as one of the corners in
      the N-dimensional hypercube spanned by the two parents.
      @param cvariables array of variable indexes to do crossover on. 
  */
  public static RealNumericalGenome crossover(Genome genome1, Genome genome2, int[] cvariables)
    {
      int i, curvar;
      double A;
      RealNumericalGenome offspring, gen1, gen2;

      UsefulRoutines.QSort(cvariables);
      offspring = new RealNumericalGenome(genome1.framework, false);
      gen1 = (RealNumericalGenome)genome1;
      gen2 = (RealNumericalGenome)genome2;
      curvar = 0;

      for (i=0;i<gen1.rnf.dimensions;i++) {
	
	if (curvar<cvariables.length)
	  if (i == cvariables[curvar]) {
	    A = Math.random();
	    offspring.realpos[i] = A*(gen1.realpos[i])+(1-A)*gen2.realpos[i];
	  }
	  else {
	    if (UsefulRoutines.randomBoolean())
	      offspring.realpos[i] = gen1.realpos[i];
	    else
	      offspring.realpos[i] = gen2.realpos[i];
	  }
	else {
	  if (UsefulRoutines.randomBoolean())
	    offspring.realpos[i] = gen1.realpos[i];
	  else
	    offspring.realpos[i] = gen2.realpos[i];
	}
      }
      return offspring;
    }

  /** Set sigma-vector for this individual. */
  public void setSigmas(double[] newsigmas)
    {
      sigmas = newsigmas;
    }

  public Object clone()
    {
      RealNumericalGenome res;
      int i;

      res = new RealNumericalGenome(rnf, false);

      for (i=0;i<realpos.length;i++) {
	res.realpos[i] = this.realpos[i];
	res.sigmas[i] = this.sigmas[i];
      }

      res.fitness = this.fitness;
      res.fitnesscalculated = this.fitnesscalculated;
      res.realposcalculated = this.realposcalculated;
      return res;
    }
}
